package com.qzuser.sendmessagebasic.client;

import com.qzuser.sendmessagebasic.client.req.*;
import com.qzuser.sendmessagebasic.client.resp.RespPayload;
import com.qzuser.sendmessagebasic.common.exception.ChannelException;
import com.qzuser.sendmessagebasic.common.exception.ToolException;
import com.qzuser.sendmessagebasic.common.tool.BinaryTool;
import com.qzuser.sendmessagebasic.server.exchange.ExchangeType;
import com.qzuser.sendmessagebasic.server.message.BasicProperties;
import com.qzuser.sendmessagebasic.server.queue.Consumer;

import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

public class Channel {
    private String channelId;
    private Connection connection;
    private final ConcurrentHashMap<String, RespPayload> respMap = new ConcurrentHashMap<>();
    private Consumer consumer = null;

    public Channel(String channelId, Connection connection) {
        this.channelId = channelId;
        this.connection = connection;
    }

    public boolean createChannel() {
        try {
            ReqPayload reqPayload = new ReqPayload();
            reqPayload.setRid(generateRid());
            reqPayload.setChannelId(channelId);
            byte[] byteArray = BinaryTool.toByteArray(reqPayload);
            Request request = new Request();
            request.setType(0x1);
            request.setLength(byteArray.length);
            request.setPayload(byteArray);
            connection.writeRequest(request);
            RespPayload respPayload = waitResult(reqPayload.getRid());
            return respPayload.getOk();
        } catch (ToolException |InterruptedException e) {
            e.printStackTrace();
            return false;
        }
    }

    private String generateRid() {
        return "R-" + UUID.randomUUID().toString();
    }

    private RespPayload waitResult(String rid) throws InterruptedException {
        RespPayload respPayload;
        while ((respPayload = respMap.get(rid)) == null) {
            synchronized (this) {
                wait();
            }
        }
        respMap.remove(rid);
        return respPayload;
    }

    public void putRespPayload(RespPayload respPayload) {
        respMap.put(respPayload.getRid(), respPayload);
        synchronized (this) {
            notifyAll();
        }
    }

    public boolean close() {
        try {
            ReqPayload reqPayload = new ReqPayload();
            reqPayload.setRid(generateRid());
            reqPayload.setChannelId(channelId);
            byte[] byteArray = BinaryTool.toByteArray(reqPayload);
            Request request = new Request();
            request.setType(0x2);
            request.setLength(byteArray.length);
            request.setPayload(byteArray);
            connection.writeRequest(request);
            RespPayload respPayload = waitResult(reqPayload.getRid());
            return respPayload.getOk();
        } catch (ToolException | InterruptedException e) {
            e.printStackTrace();
            return false;
        }
    }

    public boolean exchangeDeclare(String exchangeName, ExchangeType exchangeType, boolean durable, boolean autoDelte, Map<String, Object> arguments) {
        try {
            ExchangeEntity exchange = new ExchangeEntity();
            exchange.setRid(generateRid());
            exchange.setChannelId(channelId);
            exchange.setName(exchangeName);
            exchange.setType(exchangeType);
            exchange.setDurable(durable);
            exchange.setAutoDelete(autoDelte);
            exchange.setArguments(arguments);
            byte[] byteArray = BinaryTool.toByteArray(exchange);
            Request request = new Request();
            request.setType(0x3);
            request.setLength(byteArray.length);
            request.setPayload(byteArray);
            connection.writeRequest(request);
            RespPayload respPayload = waitResult(exchange.getRid());
            return respPayload.getOk();
        } catch (ToolException | InterruptedException e) {
            e.printStackTrace();
            return false;
        }
    }

    public boolean exchangeDelete(String exchangeName) {
        try {
            ExchangeEntity exchange = new ExchangeEntity();
            exchange.setRid(generateRid());
            exchange.setChannelId(channelId);
            exchange.setName(exchangeName);
            byte[] byteArray = BinaryTool.toByteArray(exchange);
            Request request = new Request();
            request.setType(0x4);
            request.setLength(byteArray.length);
            request.setPayload(byteArray);
            connection.writeRequest(request);
            RespPayload respPayload = waitResult(exchange.getRid());
            return respPayload.getOk();
        } catch (ToolException | InterruptedException e) {
            e.printStackTrace();
            return false;
        }
    }

    public boolean queueDeclare(String queueName, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments) {
        try {
            QueueEntity queue = new QueueEntity();
            queue.setRid(generateRid());
            queue.setChannelId(channelId);
            queue.setName(queueName);
            queue.setDurable(durable);
            queue.setExclusive(exclusive);
            queue.setAutoDelete(autoDelete);
            queue.setArguments(arguments);
            byte[] byteArray = BinaryTool.toByteArray(queue);
            Request request = new Request();
            request.setType(0x5);
            request.setLength(byteArray.length);
            request.setPayload(byteArray);
            connection.writeRequest(request);
            RespPayload respPayload = waitResult(queue.getRid());
            return respPayload.getOk();
        } catch (ToolException | InterruptedException e) {
            e.printStackTrace();
            return false;
        }
    }

    public boolean queueDelete(String queueName) {
        try {
            QueueEntity queue = new QueueEntity();
            queue.setRid(generateRid());
            queue.setChannelId(channelId);
            queue.setName(queueName);
            byte[] byteArray = BinaryTool.toByteArray(queue);
            Request request = new Request();
            request.setType(0x6);
            request.setLength(byteArray.length);
            request.setPayload(byteArray);
            connection.writeRequest(request);
            RespPayload respPayload = waitResult(queue.getRid());
            return respPayload.getOk();
        } catch (ToolException | InterruptedException e) {
            e.printStackTrace();
            return false;
        }
    }

    public boolean queueBind(String exchangeName, String queueName, String bindingKey) {
        try {
            BindEntity binding = new BindEntity();
            binding.setRid(generateRid());
            binding.setChannelId(channelId);
            binding.setExchangeName(exchangeName);
            binding.setQueueName(queueName);
            binding.setBindingKey(bindingKey);
            byte[] byteArray = BinaryTool.toByteArray(binding);
            Request request = new Request();
            request.setType(0x7);
            request.setLength(byteArray.length);
            request.setPayload(byteArray);
            connection.writeRequest(request);
            RespPayload respPayload = waitResult(binding.getRid());
            return respPayload.getOk();
        } catch (ToolException | InterruptedException e) {
            e.printStackTrace();
            return false;
        }
    }

    public boolean queueUnBind(String exchangeName, String queueName) {
        try {
            BindEntity binding = new BindEntity();
            binding.setRid(generateRid());
            binding.setChannelId(channelId);
            binding.setExchangeName(exchangeName);
            binding.setQueueName(queueName);
            byte[] byteArray = BinaryTool.toByteArray(binding);
            Request request = new Request();
            request.setType(0x8);
            request.setLength(byteArray.length);
            request.setPayload(byteArray);
            connection.writeRequest(request);
            RespPayload respPayload = waitResult(binding.getRid());
            return respPayload.getOk();
        } catch (ToolException |InterruptedException e) {
            e.printStackTrace();
            return false;
        }
    }

    public boolean basicPublish(String exchangeName, String routingKey, BasicProperties properties, byte[] body) {
        try {
            PublishEntity publish = new PublishEntity();
            publish.setRid(generateRid());
            publish.setChannelId(channelId);
            publish.setExchangeName(exchangeName);
            publish.setRoutingKey(routingKey);
            publish.setProperties(properties);
            publish.setBody(body);
            byte[] byteArray = BinaryTool.toByteArray(publish);
            Request request = new Request();
            request.setType(0x9);
            request.setLength(byteArray.length);
            request.setPayload(byteArray);
            connection.writeRequest(request);
            RespPayload respPayload = waitResult(publish.getRid());
            return respPayload.getOk();
        } catch (ToolException | InterruptedException e) {
            e.printStackTrace();
            return false;
        }
    }

    public boolean basicConsume(String queueName, boolean autoAck, Consumer consumer) {
        try {
            if (this.consumer != null) {
                throw new ChannelException("[Channel] 一个channel只允许一个回调");
            }
            this.consumer = consumer;
            ConsumeEntity consume = new ConsumeEntity();
            consume.setRid(generateRid());
            consume.setChannelId(channelId);
            consume.setConsumerTag(channelId);
            consume.setQueueName(queueName);
            consume.setAutoAck(autoAck);
            byte[] byteArray = BinaryTool.toByteArray(consume);
            Request request = new Request();
            request.setType(0xa);
            request.setLength(byteArray.length);
            request.setPayload(byteArray);
            connection.writeRequest(request);
            RespPayload respPayload = waitResult(consume.getRid());
            return respPayload.getOk();
        } catch (ChannelException | ToolException | InterruptedException e) {
            e.printStackTrace();
            return false;
        }
    }

    public boolean basicAck(String queueName, String messageId) {
        try {
            HandAckEntity ack = new HandAckEntity();
            ack.setRid(generateRid());
            ack.setChannelId(channelId);
            ack.setQueueName(queueName);
            ack.setMessageId(messageId);
            byte[] byteArray = BinaryTool.toByteArray(ack);
            Request request = new Request();
            request.setType(0xb);
            request.setLength(byteArray.length);
            request.setPayload(byteArray);
            connection.writeRequest(request);
            RespPayload respPayload = waitResult(ack.getRid());
            return respPayload.getOk();
        } catch (ToolException | InterruptedException e) {
            e.printStackTrace();
            return false;
        }
    }

    public String getChannelId() {
        return channelId;
    }

    public void setChannelId(String channelId) {
        this.channelId = channelId;
    }

    public Connection getConnection() {
        return connection;
    }

    public void setConnection(Connection connection) {
        this.connection = connection;
    }

    public ConcurrentHashMap<String, RespPayload> getRespMap() {
        return respMap;
    }

    public Consumer getConsumer() {
        return consumer;
    }

    public void setConsumer(Consumer consumer) {
        this.consumer = consumer;
    }
}
